
#ifndef MLOADER_IOSTREAM
#define MLOADER_IOSTREAM

#include "Common.h"
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

namespace MLoader
{
    class IOStream
    {
    public:
        typedef enum  SeekPos {
            Set = 0,
            Cur = 1,
            End = 2
        } SeekPos;
        
        IOStream() = delete;
        IOStream(const std::string strFilename, const char* mode = "rb"):_size(SIZE_MAX)
        {
            _file = ::fopen(strFilename.c_str(), mode);
            _file_name = (NULL != _file) ? strFilename : "";
        }
        virtual ~IOStream() { if (_file) ::fclose(_file); }

        size_t Read(void* pvBuffer, size_t pSize, size_t pCount)
        {
            ML_ASSERT(NULL != pvBuffer && 0 != pSize && 0 != pCount);
            return (_file ? ::fread(pvBuffer, pSize, pCount, _file) : 0);
        }
        size_t Write(const void* pvBuffer, size_t pSize, size_t pCount)
        {
            ML_ASSERT(NULL != pvBuffer && 0 != pSize && 0 != pCount);
            return (_file ? ::fwrite(pvBuffer, pSize, pCount, _file) : 0);
        }

        Flag Seek(const size_t& pOffset, const SeekPos& pos)
        {
            if (!_file) return Flag::Fail;

            // Just to check whether our enum maps one to one with the CRT constants
            static_assert(SeekPos::Cur == SEEK_CUR && SeekPos::End == SEEK_END && SeekPos::Set == SEEK_SET, "Not SEEK_CUR SEEK_END SEEK_SET");

            // do the seek
            return (0 == ::fseek(_file, (long)pOffset,(int)pos) ? Flag::Success : Flag::Fail);
        }

        size_t Tell() const
        {
            return (_file ? ::ftell(_file) : 0);
        }
        
        /**
         * Although fseek/ftell would allow us to reuse the exising file handle here,
         * it is generally unsafe because:
         *  - For binary streams, it is not technically well-defined
         *  - For text files the results are meaningless
         *
         * That's why we use the safer variant fstat here.
         * See here for details:
         * https://www.securecoding.cert.org/confluence/display/seccode/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file
         */
        size_t FileSize() const
        {
            if (!_file || _file_name.empty()) return 0;
            
            if (SIZE_MAX == _size) {
#if defined _WIN32 && !defined __GNUC__
                struct __stat64 fileStat;
                int err = _stat64(_file_name.c_str(), &fileStat);
                if (0 != err)
                    return 0;
                _size = (size_t) (fileStat.st_size);
#else
                struct stat fileStat;
                int err = stat(_file_name.c_str(), &fileStat);
                if (0 != err)
                    return 0;
                _size = (size_t) (fileStat.st_size);
#endif
            }
            
            return _size;
        }

        void Flush()
        {
            if (_file) ::fflush(_file);
        }

    private:
        FILE* _file;
        std::string _file_name;

        mutable size_t _size;
    };
}


#endif // MLOADER_IOSTREAM
